
/******************************************************************************
 * MODULE     : pdf_hummus_renderer.cpp
 * DESCRIPTION: Renderer for printing pdf graphics using the PDFHummus library
 * COPYRIGHT  : (C) 2012 Massimiliano Gubinelli
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include "pdf_hummus_renderer.hpp"
#include "Freetype/tt_file.hpp"
#include "Metafont/tex_files.hpp"
#include "analyze.hpp"
#include "convert.hpp"
#include "converter.hpp"
#include "file.hpp"
#include "font.hpp"
#include "frame.hpp"
#include "image_files.hpp"
#include "iterator.hpp"
#include "link.hpp"
#include "merge_sort.hpp"
#include "ntuple.hpp"
#include "pdf.hpp"
#include "pdf_image.hpp"
#include "scheme.hpp"
#include "sys_utils.hpp"
#include "wencoding.hpp"

#include <lolly/data/numeral.hpp>
#include <moebius/data/scheme.hpp>
using lolly::data::as_hexadecimal;
using moebius::data::block_to_scheme_tree;

#ifdef QT_CORE_LIB
#include <QtCore>
#endif
#ifdef QTTEXMACS
#include "Qt/qt_picture.hpp"
#include "Qt/qt_utilities.hpp"
#include <QImage>
#endif

#include "PDFWriter/DictionaryContext.h"
#include "PDFWriter/InfoDictionary.h"
#include "PDFWriter/InputByteArrayStream.h"
#include "PDFWriter/OutputStreamTraits.h"
#include "PDFWriter/PDFDocumentCopyingContext.h"
#include "PDFWriter/PDFFormXObject.h"
#include "PDFWriter/PDFImageXObject.h"
#include "PDFWriter/PDFPage.h"
#include "PDFWriter/PDFPageInput.h"
#include "PDFWriter/PDFStream.h"
#include "PDFWriter/PDFTiledPattern.h"
#include "PDFWriter/PDFUsedFont.h"
#include "PDFWriter/PDFWriter.h"
#include "PDFWriter/PageContentContext.h"
#include "PDFWriter/ProcsetResourcesConstants.h"
#include "PDFWriter/TiledPatternContentContext.h"
#include "PDFWriter/XObjectContentContext.h"

/******************************************************************************
 * pdf_hummus_renderer
 ******************************************************************************/

static EPDFVersion ePDFVersion= ePDFVersion14;

typedef triple<int, int, int>               rgb;
typedef quartet<string, int, SI, SI>        dest_data;
typedef quintuple<string, int, SI, SI, int> outline_data;

class t3font;
class pdf_pattern;

class pdf_hummus_renderer_rep : public renderer_rep {

  static const int default_dpi=
      72;         // PDF initial coordinate system corresponds to 72 dpi
  bool   started; // initialisation is OK
  url    pdf_file_name;
  int    dpi;
  int    nr_pages;
  string page_type;
  bool   landscape;
  double paper_w;
  double paper_h;

  ObjectIDType initial_GState_id; // GSstate with default values

  int    page_num;
  bool   inText;
  color  fg, bg;
  SI     lw;
  double current_width;
  int    clip_level;

  pencil pen;
  brush  bgb, fgb;

  string       cfn;
  PDFUsedFont* cfid;
  double       fsize;
  double       prev_text_x, prev_text_y;

  double width, height;

  hashmap<string, PDFUsedFont*>        native_fonts;
  hashset<string>                      not_native_fonts;
  hashset<string>                      EuropeanComputerModern_fonts;
  hashmap<string, pdf_raw_image>       pdf_glyphs;
  hashmap<tree, pdf_image>             image_pool;
  hashmap<tree, pdf_image>             pattern_image_pool;
  hashmap<tree, pdf_pattern>           pattern_pool;
  hashmap<unsigned long long int, url> picture_cache;
  array<url>                           temp_images;

  hashmap<int, ObjectIDType> alpha_id;
  hashmap<int, ObjectIDType> page_id;
  int                        t3font_registry_id;
  hashmap<string, t3font>    t3font_list;

  // link annotation support
  hashmap<ObjectIDType, string> annot_list;
  list<dest_data>               dests;
  ObjectIDType                  destId;
  hashmap<string, int>          label_id;
  int                           label_count;
  hashmap<string, string>       metadata;

  // outline support
  ObjectIDType       outlineId;
  list<outline_data> outlines;

  PDFWriter           pdfWriter;
  PDFPage*            page;
  PageContentContext* contentContext;

  // geometry

  double to_x (SI x) {
    x+= ox;
    if (x >= 0) x= x / pixel;
    else x= (x - pixel + 1) / pixel;
    return x;
  };

  double to_y (SI y) {
    y+= oy;
    if (y >= 0) y= y / pixel;
    else y= (y - pixel + 1) / pixel;
    return y;
  };

  // various internal routines

  void init_page_size ();
  void select_stroke_color (color c);
  void select_fill_color (color c);
  void select_alpha (int a);

  void register_pattern_image (brush br, SI pixel);
  void select_stroke_pattern (brush br);
  void select_fill_pattern (brush br);

  void select_line_width (SI w);
  void compile_glyph (scheme_tree t);
  void begin_text ();
  void end_text ();

  void begin_page ();
  void end_page ();

  int get_label_id (string label);

  // various internal routines
  void             flush_images ();
  void             flush_patterns ();
  void             flush_glyphs ();
  void             flush_dests ();
  void             flush_outlines ();
  void             flush_fonts ();
  PDFImageXObject* create_pdf_image_raw (string raw_data, SI width, SI height,
                                         ObjectIDType imageXObjectID);
  void             make_pdf_font (string fontname);
  void             draw_bitmap_glyph (int ch, font_glyphs fn, SI x, SI y);
  void             image (url u, double w, double h, SI x, SI y, int alpha);

  void bezier_arc (SI x1, SI y1, SI x2, SI y2, int alpha, int delta,
                   bool filled);

  // hooks for the Hummus library

  class DestinationsWriter : public DocumentContextExtenderAdapter {
    pdf_hummus_renderer_rep* ren;

  public:
    DestinationsWriter (pdf_hummus_renderer_rep* _ren) : ren (_ren) {}
    virtual ~DestinationsWriter () {}

    // IDocumentContextExtender implementation
    virtual PDFHummus::EStatusCode
    OnCatalogWrite (CatalogInformation*         inCatalogInformation,
                    DictionaryContext*          inCatalogDictionaryContext,
                    ObjectsContext*             inPDFWriterObjectContext,
                    PDFHummus::DocumentContext* inDocumentContext) {
      ren->on_catalog_write (inCatalogInformation, inCatalogDictionaryContext,
                             inPDFWriterObjectContext, inDocumentContext);
      return eSuccess;
    }
  };

  PDFHummus::EStatusCode
  on_catalog_write (CatalogInformation*         inCatalogInformation,
                    DictionaryContext*          inCatalogDictionaryContext,
                    ObjectsContext*             inPDFWriterObjectContext,
                    PDFHummus::DocumentContext* inDocumentContext);

  void recurse (ObjectsContext& objectsContext, int parent_ls,
                list<outline_data>& it, ObjectIDType parentId,
                ObjectIDType& firstId, ObjectIDType& lastId, int& count);

public:
  pdf_hummus_renderer_rep (url pdf_file_name, int dpi, int nr_pages,
                           string ptype, bool landsc, double paper_w,
                           double paper_h);
  ~pdf_hummus_renderer_rep ();
  bool is_printer ();
  bool is_started ();
  void next_page ();

  void set_transformation (frame fr);
  void reset_transformation ();

  void set_clipping (SI x1, SI y1, SI x2, SI y2, bool restore= false);

  pencil get_pencil ();
  brush  get_background ();
  void   set_pencil (pencil pen2);
  void   set_brush (brush b2);
  void   set_background (brush b2);

  void draw (int char_code, font_glyphs fn, SI x, SI y);
  void line (SI x1, SI y1, SI x2, SI y2);
  void lines (array<SI> x, array<SI> y);
  void clear (SI x1, SI y1, SI x2, SI y2);
  void fill (SI x1, SI y1, SI x2, SI y2);
  void arc (SI x1, SI y1, SI x2, SI y2, int alpha, int delta);
  void fill_arc (SI x1, SI y1, SI x2, SI y2, int alpha, int delta);
  void polygon (array<SI> x, array<SI> y, bool convex= true);

  void     draw_picture (picture p, SI x, SI y, int alpha);
  void     draw_scalable (scalable im, SI x, SI y, int alpha);
  renderer shadow (picture& pic, SI x1, SI y1, SI x2, SI y2);
  void     fetch (SI x1, SI y1, SI x2, SI y2, renderer ren, SI x, SI y);
  void     new_shadow (renderer& ren);
  void     delete_shadow (renderer& ren);
  void     get_shadow (renderer ren, SI x1, SI y1, SI x2, SI y2);
  void     put_shadow (renderer ren, SI x1, SI y1, SI x2, SI y2);
  void     apply_shadow (SI x1, SI y1, SI x2, SI y2);

  /************************ subroutines hyperlinks ***************************/

  void anchor (string label, SI x1, SI y1, SI x2, SI y2);
  void href (string label, SI x1, SI y1, SI x2, SI y2);
  void toc_entry (string kind, string title, SI x, SI y);
  void set_metadata (string kind, string val);
  void flush_metadata ();
};

/******************************************************************************
 * local utilities
 ******************************************************************************/

static void
write_indirect_obj (ObjectsContext& objectsContext, ObjectIDType destId,
                    string payload) {
  objectsContext.StartNewIndirectObject (destId);
  c_string buf (payload);
  objectsContext.StartFreeContext ()->Write ((unsigned char*) (char*) buf,
                                             N (payload));
  objectsContext.EndFreeContext ();
  objectsContext.EndIndirectObject ();
}

/******************************************************************************
 * constructors and destructors
 ******************************************************************************/

pdf_hummus_renderer_rep::pdf_hummus_renderer_rep (
    url pdf_file_name2, int dpi2, int nr_pages2, string page_type2,
    bool landscape2, double paper_w2, double paper_h2)
    : renderer_rep (false), pdf_file_name (pdf_file_name2), dpi (dpi2),
      nr_pages (nr_pages2), page_type (page_type2), landscape (landscape2),
      paper_w (paper_w2), paper_h (paper_h2), page_num (0), inText (false),
      fg (-1), bg (-1), lw (-1), pen (black), bgb (white), fgb (black),
      cfn (""), cfid (NULL), native_fonts (NULL), t3font_registry_id (-1),
      destId (0), label_count (0), outlineId (0) {
  width = default_dpi * paper_w / 2.54;
  height= default_dpi * paper_h / 2.54;

  // setup library

  EStatusCode status;
  ePDFVersion   = ePDFVersion14; // PDF 1.4 for alpha
  string version= pdf_version ();
  if (version == "1.5") ePDFVersion= ePDFVersion15;
  if (version == "1.6") ePDFVersion= ePDFVersion16;
  if (version == "1.7") ePDFVersion= ePDFVersion17;
  // LogConfiguration log (true, true, "PDFWriterLog.txt");
  LogConfiguration    log     = LogConfiguration::DefaultLogConfiguration ();
  bool                compress= true;
  PDFCreationSettings settings (
      compress, true); //, EncryptionOptions("user", 4, "owner"));
  status= pdfWriter.StartPDF (as_charp (concretize (pdf_file_name)),
                              ePDFVersion, log, settings);
  if (status != PDFHummus::eSuccess) {
    convert_error << "failed to start PDF\n";
    started= false;
  }
  else {
    started= true;
    pdfWriter.GetDocumentContext ().AddDocumentContextExtender (
        new DestinationsWriter (this));

    // create a graphic state with default values
    initial_GState_id= pdfWriter.GetObjectsContext ()
                           .GetInDirectObjectsRegistry ()
                           .AllocateNewObjectID ();
    ObjectsContext& objectsContext= pdfWriter.GetObjectsContext ();
    objectsContext.StartNewIndirectObject (initial_GState_id);
    std::stringstream buf;
    buf << "<< /Type /ExtGState\r\n";
    buf << "/LW 1.0\r\n";
    buf << "/LC 0\r\n";
    buf << "/LJ 0\r\n";
    buf << "/ML 10.0\r\n";
    // buf << "/D [[] 0]\r\n"; // useless
    buf << "/RI /RelativeColorimetric\r\n";
    buf << "/OP false\r\n";
    buf << "/op false\r\n";
    buf << "/FL 1.0\r\n";
    buf << "/SA false\r\n";
    buf << "/BM /Normal\r\n";
    buf << "/SMask /None\r\n";
    buf << "/CA 1.0\r\n";
    buf << "/ca 1.0\r\n";
    buf << "/AIS false\r\n";
    buf << "/TK true\r\n";
    buf << ">>\r\n";
    objectsContext.StartFreeContext ()->Write (
        (const IOBasicTypes::Byte*) (buf.str ().c_str ()), buf.str ().size ());
    objectsContext.EndFreeContext ();
    objectsContext.EndIndirectObject ();

    // start real work
    begin_page ();
  }
}

pdf_hummus_renderer_rep::~pdf_hummus_renderer_rep () {
  if (!started) return; // no cleanup to do
  end_page ();

  flush_images ();
  flush_patterns ();
  flush_glyphs ();
  flush_dests ();
  flush_outlines ();
  flush_fonts ();
  flush_metadata ();

  {
    // flush alphas
    iterator<int>   it            = iterate (alpha_id);
    ObjectsContext& objectsContext= pdfWriter.GetObjectsContext ();
    while (it->busy ()) {
      int    a = it->next ();
      double da= ((double) a) / 1000.0;
      objectsContext.StartNewIndirectObject (alpha_id (a));
      {
        std::stringstream buf;
        buf << "<< /Type /ExtGState /CA " << da << "  /ca " << da << " >>\r\n";
        objectsContext.StartFreeContext ()->Write (
            (const IOBasicTypes::Byte*) (buf.str ().c_str ()),
            buf.str ().size ());
        objectsContext.EndFreeContext ();
      }
      objectsContext.EndIndirectObject ();
    }
  }

  {
    // flush annotations
    iterator<ObjectIDType> it            = iterate (annot_list);
    ObjectsContext&        objectsContext= pdfWriter.GetObjectsContext ();
    while (it->busy ()) {
      ObjectIDType id= it->next ();
      write_indirect_obj (objectsContext, id, annot_list (id));
    }
  }

  EStatusCode status= pdfWriter.EndPDF ();
  if (status != PDFHummus::eSuccess) {
    convert_error << "Failed in end PDF\n";
  }

  // remove temporary pictures
  for (int i= 0; i < N (temp_images); i++)
    if (!is_none (temp_images[i]) && exists (temp_images[i]))
      remove (temp_images[i]);
}

bool
pdf_hummus_renderer_rep::is_printer () {
  // debug_convert << "is_printer\n";
  return true;
}

bool
pdf_hummus_renderer_rep::is_started () {
  return started;
}

void
pdf_hummus_renderer_rep::next_page () {
  end_page ();
  begin_page ();
}

void
pdf_hummus_renderer_rep::begin_page () {
  // EStatusCode status;

  page= new PDFPage ();
  page->SetMediaBox (PDFRectangle (0, 0, width, height));
  contentContext= pdfWriter.StartPageContentContext (page);
  if (NULL == contentContext) {
    // status = PDFHummus::eFailure;
    convert_error << "Failed to create content context for page\n";
  }
  else {
    fg           = -1;
    bg           = -1;
    lw           = -1;
    current_width= -1.0;
    cfn          = "";
    cfid         = NULL;
    inText       = false;
    clip_level   = 0;

    // outmost save of the graphics state
    contentContext->q ();
    // set scaling suitable for dpi (pdf default is 72)
    contentContext->cm ((double) default_dpi / dpi, 0, 0,
                        (double) default_dpi / dpi, 0, 0);

    set_origin (0, paper_h * dpi * pixel / 2.54);
    set_clipping (0, (int) ((-dpi * pixel * paper_h) / 2.54),
                  (int) ((dpi * pixel * paper_w) / 2.54), 0);
  }
}

void
pdf_hummus_renderer_rep::end_page () {

  if (!page) return;
  EStatusCode status;

  end_text ();

  // reset set_clipping calls in order to have well formed PDF.
  while (clip_level--)
    contentContext->Q ();

  // outmost restore for the graphics state (see begin_page)
  contentContext->Q ();

  status= pdfWriter.EndPageContentContext (contentContext);
  if (status != PDFHummus::eSuccess) {
    convert_error << "Failed to end page content context\n";
  }

  EStatusCodeAndObjectIDType res=
      pdfWriter.GetDocumentContext ().WritePageAndRelease (page);
  status= res.first;
  if (status != PDFHummus::eSuccess) {
    convert_error << "Failed to write page and release\n";
  }

  page_id (page_num)= res.second;
  page_num++;
}

void
pdf_hummus_renderer_rep::begin_text () {
  if (!inText) {
    contentContext->BT ();
    inText     = true;
    prev_text_x= to_x (0);
    prev_text_y= to_y (0);
    contentContext->Tm (1, 0, 0, 1, prev_text_x, prev_text_y);
  }
}

void
pdf_hummus_renderer_rep::end_text () {
  if (inText) {
    contentContext->ET ();
    inText= false;
  }
}

/******************************************************************************
 * Transformed rendering
 ******************************************************************************/

void
pdf_hummus_renderer_rep::set_transformation (frame fr) {
  ASSERT (fr->linear, "only linear transformations have been implemented");

  end_text ();

  SI cx1, cy1, cx2, cy2;
  get_clipping (cx1, cy1, cx2, cy2);
  rectangle oclip (cx1, cy1, cx2, cy2);
  frame     cv= scaling (point (pixel, pixel), point (-ox, -oy));
  frame     tr= invert (cv) * fr * cv;
  point     o = tr (point (0.0, 0.0));
  point     ux= tr (point (1.0, 0.0)) - o;
  point     uy= tr (point (0.0, 1.0)) - o;
  // debug_convert << "Set transformation " << o << ", " << ux << ", " << uy <<
  // "\n";
  double tx= o[0];
  double ty= o[1];
  contentContext->q ();
  contentContext->cm (ux[0], ux[1], uy[0], uy[1], tx, ty);

  rectangle nclip= fr[oclip];
  renderer_rep::clip (nclip->x1, nclip->y1, nclip->x2, nclip->y2);
}

void
pdf_hummus_renderer_rep::reset_transformation () {
  end_text ();
  renderer_rep::unclip ();
  contentContext->Q ();
}

/******************************************************************************
 * Clipping
 ******************************************************************************/

void
pdf_hummus_renderer_rep::set_clipping (SI x1, SI y1, SI x2, SI y2,
                                       bool restore) {
  renderer_rep::set_clipping (x1, y1, x2, y2, restore);

  end_text ();

  outer_round (x1, y1, x2, y2);
  if (restore) {
    // debug_convert << "restore clipping\n";
    if (clip_level > 0) {
      contentContext->Q ();
      clip_level--;
    }
    cfn= "";
  }
  else {
    // debug_convert << "set clipping\n";
    contentContext->q ();
    clip_level++;
    double xx1= to_x (min (x1, x2));
    double yy1= to_y (min (y1, y2));
    double xx2= to_x (max (x1, x2));
    double yy2= to_y (max (y1, y2));
    contentContext->re (xx1, yy1, xx2 - xx1, yy2 - yy1);
    contentContext->W ();
    contentContext->n ();
  }
}

/******************************************************************************
 * Tiled patterns
 ******************************************************************************/

class pdf_pattern_rep : public concrete_struct {
public:
  pdf_image    im;
  SI           w, h, sx, sy;
  double       scale_x, scale_y;
  ObjectIDType id;

  pdf_pattern_rep (pdf_image _im, SI _w, SI _h, SI _sx, SI _sy, double _scale_x,
                   double _scale_y, ObjectIDType _id)
      : im (_im), w (_w), h (_h), sx (_sx), sy (_sy), scale_x (_scale_x),
        scale_y (_scale_y), id (_id) {}
  ~pdf_pattern_rep () {}

  void flush (PDFWriter& pdfw) {
    const double matrix[]= {scale_x, 0, 0, scale_y, (double) sx, (double) sy};
    DocumentContext& documentContext= pdfw.GetDocumentContext ();
    PDFTiledPattern* tiledPattern=
        documentContext.StartTiledPattern (1, // int inPaintType,
                                           2, // int inTilingType,
                                           PDFRectangle (0, 0, w, h),
                                           w, // double inXStep,
                                           h, // double inYStep,
                                           id, matrix);
    TiledPatternContentContext* tiledPatternContentContext=
        tiledPattern->GetContentContext ();
    std::string imageName=
        tiledPattern->GetResourcesDictionary ().AddImageXObjectMapping (im->id);
    tiledPatternContentContext->q ();
    tiledPatternContentContext->cm (w, 0, 0, h, 0, 0);
    tiledPatternContentContext->Do (imageName);
    tiledPatternContentContext->Q ();
    PDFHummus::EStatusCode st=
        documentContext.EndTiledPatternAndRelease (tiledPattern);
    if (st != PDFHummus::eSuccess)
      convert_error << "Cannot flush tiled pattern " << im->u << "\n";
  }
};

class pdf_pattern {
  CONCRETE_NULL (pdf_pattern);
  pdf_pattern (pdf_image _im, SI _w, SI _h, SI _sx, SI _sy, double _scale_x,
               double _scale_y, ObjectIDType _id)
      : rep (tm_new<pdf_pattern_rep> (_im, _w, _h, _sx, _sy, _scale_x, _scale_y,
                                      _id)){};
};

CONCRETE_NULL_CODE (pdf_pattern);

void
pdf_hummus_renderer_rep::register_pattern_image (brush br, SI pixel) {
  // debug_convert << "register_pattern_image\n";
  if (is_nil (br) || br->get_type () != brush_pattern) {
    convert_warning << "register_pattern_image: "
                    << "brush with pattern expected\n";
    return;
  }
  tree p= br->get_pattern ();
  // debug_convert << p << "\n";
  if (pattern_pool->contains (p)) return;

  url  u;
  SI   w, h;
  tree eff;
  get_pattern_data (u, w, h, eff, br, pixel);
  tree key= tuple (as_tree (u), as_string (w), as_string (h), eff);

  pdf_image image_pdf;
  if (pattern_image_pool->contains (key)) image_pdf= pattern_image_pool[key];
  else {
    // debug_convert << "Insert pattern image\n";
    QImage* pim= get_image (u, w, h, eff, pixel);
    if (pim == NULL) {
      convert_error << "Cannot read image file '" << u << "'"
                    << " with get_image" << LF;
      return;
    }
    if (w != pim->width () || h != pim->height ()) {
      convert_error << "Invalid image size '" << u << "'"
                    << " after get_image" << LF;
      return;
    }
    url temp= url_temp ("png");
    pim->save (utf8_to_qstring (concretize (temp)), "PNG");
    temp_images << temp;
    ObjectIDType image_id= pdfWriter.GetObjectsContext ()
                               .GetInDirectObjectsRegistry ()
                               .AllocateNewObjectID ();
    image_pdf               = pdf_image (temp, image_id);
    pattern_image_pool (key)= image_pdf;
  }
  // debug_convert << "  insert pattern\n";
  ObjectIDType id= pdfWriter.GetObjectsContext ()
                       .GetInDirectObjectsRegistry ()
                       .AllocateNewObjectID ();
  // debug_convert << "pdf_pattern " << ox << ", " << oy
  //  		   << ", " << pixel << ", " << shrinkf
  //		   << ", " << zoomf << LF;
  // debug_convert << "            " << to_x(0) << ", " << to_y(0) << LF;
  // debug_convert << "            " << w << ", " << h << LF;
  pdf_pattern p_pdf (image_pdf, w, h, width + to_x (0), height, // FIXME ???
                     ((double) default_dpi) / dpi, ((double) default_dpi) / dpi,
                     id);
  pattern_pool (p)= p_pdf;
}

/******************************************************************************
 * Graphic state management
 ******************************************************************************/

void
pdf_hummus_renderer_rep::select_alpha (int a) {
  if (!alpha_id->contains (a)) {
    ObjectIDType temp= pdfWriter.GetObjectsContext ()
                           .GetInDirectObjectsRegistry ()
                           .AllocateNewObjectID ();
    alpha_id (a)= temp;
  }
  std::string name=
      page->GetResourcesDictionary ().AddExtGStateMapping (alpha_id (a));
  contentContext->gs (name);
}

void
pdf_hummus_renderer_rep::select_stroke_color (color c) {
  ;
  int r, g, b, a;
  get_rgb_color (c, r, g, b, a);
  r        = ((r * 1000) / 255);
  g        = ((g * 1000) / 255);
  b        = ((b * 1000) / 255);
  a        = ((a * 1000) / 255);
  double dr= ((double) r) / 1000.0;
  double dg= ((double) g) / 1000.0;
  double db= ((double) b) / 1000.0;
  contentContext->RG (dr, dg, db); // stroke color;
  select_alpha (a);
}

void
pdf_hummus_renderer_rep::select_fill_color (color c) {
  ;
  int r, g, b, a;
  get_rgb_color (c, r, g, b, a);
  r        = ((r * 1000) / 255);
  g        = ((g * 1000) / 255);
  b        = ((b * 1000) / 255);
  a        = ((a * 1000) / 255);
  double dr= ((double) r) / 1000.0;
  double dg= ((double) g) / 1000.0;
  double db= ((double) b) / 1000.0;
  contentContext->rg (dr, dg, db); // non-stroking color
  select_alpha (a);
}

void
pdf_hummus_renderer_rep::select_stroke_pattern (brush br) {
  if (is_nil (br) || br->get_type () != brush_pattern) return;
  tree p_tree= br->get_pattern ();
  register_pattern_image (br, brushpx == -1 ? pixel : brushpx);
  if (!pattern_pool->contains (p_tree)) {
    convert_error << "select_stroke_pattern: "
                  << "cannot find registered pattern\n";
    return;
  }
  pdf_pattern p= pattern_pool[p_tree];
  std::string patternName=
      page->GetResourcesDictionary ().AddPatternMapping (p->id);
  contentContext->CS ("Pattern");
  contentContext->SCN ((double*) NULL, 0, patternName);
}

void
pdf_hummus_renderer_rep::select_fill_pattern (brush br) {
  if (is_nil (br) || br->get_type () != brush_pattern) return;
  tree p_tree= br->get_pattern ();
  register_pattern_image (br, brushpx == -1 ? pixel : brushpx);
  if (!pattern_pool->contains (p_tree)) {
    convert_error << "select_fill_pattern: "
                  << "cannot find registered pattern\n";
    return;
  }
  pdf_pattern p= pattern_pool[p_tree];
  std::string patternName=
      page->GetResourcesDictionary ().AddPatternMapping (p->id);
  contentContext->cs ("Pattern");
  contentContext->scn ((double*) NULL, 0, patternName);
  select_alpha ((1000 * br->get_alpha ()) / 255);
}

void
pdf_hummus_renderer_rep::select_line_width (SI w) {
  double pw= w / pixel;
  // if (pw < 1) pw= 1;
  if (pw != current_width) {
    contentContext->w (pw);
    current_width= pw;
  }
}

pencil
pdf_hummus_renderer_rep::get_pencil () {
  // debug_convert << "get_color\n";
  return pen;
}

brush
pdf_hummus_renderer_rep::get_background () {
  // debug_convert << "get_background\n";
  return bgb;
}

void
pdf_hummus_renderer_rep::set_pencil (pencil pen2) {
  // debug_convert << "set_pencil\n";
  pen= pen2;
  lw = pen->get_width ();
  select_line_width (lw);
  color c= pen->get_color ();
  fg     = c;
  select_fill_color (c);
  select_stroke_color (c);
  if (pen->get_type () == pencil_brush) {
    // debug_convert << "pencil has brush type" << LF;
    brush br= pen->get_brush ();
    fgb     = br;
    select_fill_pattern (br);
    select_stroke_pattern (br);
  }
  if (pen->get_cap () == cap_round) contentContext->J (1); // round cap
  else contentContext->J (2);                              // square cap
  contentContext->j (1);                                   // round join
}

void
pdf_hummus_renderer_rep::set_brush (brush br) {
  // debug_convert << "set_brush\n";
  fgb= br;
  pen= pencil (br);
  set_pencil (pen); // FIXME ???
  if (is_nil (br)) return;
  if (br->get_type () == brush_none) {
    pen= pencil ();
    fgb= brush ();
  }
  else {
    select_fill_color (pen->get_color ());
    select_stroke_color (pen->get_color ());
  }
  if (br->get_type () == brush_pattern) {
    tree p_tree= br->get_pattern ();
    register_pattern_image (br, brushpx == -1 ? pixel : brushpx);
    if (!pattern_pool->contains (p_tree)) {
      convert_error << "select_stroke_pattern: "
                    << "cannot find registered pattern\n";
      return;
    }
    select_fill_pattern (br);
    select_stroke_pattern (br);
  }
  // select_alpha (br->get_alpha ());
}
void
pdf_hummus_renderer_rep::set_background (brush b) {
  // debug_convert << "set_background\n";
  // if (bgb==b) return;
  bgb= b;
  bg = b->get_color ();
}

/******************************************************************************
 * Raw images
 ******************************************************************************/

// obsolete support for direct placement of virtual glyphs without Type3 fonts.

static string
load_virtual_glyph (glyph gl) {
  string buf;
  int    i, j;
  for (j= 0; j < gl->height; j++)
    for (i= 0; i < gl->width; i++) {
      if (gl->get_x (i, j) > 0) buf << (char) 0;
      else buf << (char) 255;
    }
  return buf;
}

void
pdf_hummus_renderer_rep::flush_glyphs () {
  // flush all images
  iterator<string> it= iterate (pdf_glyphs);
  while (it->busy ()) {
    pdf_raw_image im= pdf_glyphs[it->next ()];
    im->flush (pdfWriter);
  }
}

void
pdf_hummus_renderer_rep::draw_bitmap_glyph (int ch, font_glyphs fn, SI x,
                                            SI y) {
  (void) x;
  (void) y;
  // use bitmap (to be improved)
  string fontname= fn->res_name;
  string char_name (fontname * "-" * as_string ((int) ch));
  if (!pdf_glyphs->contains (char_name)) {
    glyph gl= fn->get (ch);
    // debug_convert << "draw bitmap glyph " << (double)gl->width / 8 << " " <<
    // (double)gl->height / 8 << "\n";
    if (is_nil (gl)) return;
    string       buf           = load_virtual_glyph (gl);
    ObjectIDType imageXObjectID= pdfWriter.GetObjectsContext ()
                                     .GetInDirectObjectsRegistry ()
                                     .AllocateNewObjectID ();
    pdf_glyphs (char_name)=
        pdf_raw_image (buf, gl->width, gl->height, imageXObjectID);
  }
}

/******************************************************************************
 * Type 3 fonts
 ******************************************************************************/

static int
t3font_font_chunk (int global_glyph) {
  if (global_glyph <= 255) return 0;
  return global_glyph / 255;
}

static int
t3font_get_local_glyph (int global_glyph, int font_chunk, string font_name) {
  int chunk= t3font_font_chunk (global_glyph);
  if (chunk != font_chunk)
    convert_error << "pdf_hummus_renderer, in t3font support, glyph "
                  << as_string (global_glyph) << " out of chunk "
                  << as_string (font_chunk) << " of font " << font_name << LF;
  if (global_glyph <= 255) return global_glyph;
  return (global_glyph % 255) + 1;
}

static int
t3font_get_global_glyph (int local_glyph, int font_chunk, string font_name) {
  if (local_glyph > 255)
    convert_error << "pdf_hummus_renderer, in t3font support, local glyph "
                  << as_string (local_glyph)
                  << " is out of the range 0,...,255 "
                  << "in font " << font_name << LF;
  if (font_chunk == 0) return local_glyph;
  return 255 * font_chunk + local_glyph - 1;
}

class t3font_rep : public concrete_struct {
public:
  font_glyphs       fn;
  int               font_chunk;
  ObjectIDType      fontId;
  ObjectsContext&   objectsContext;
  hashmap<int, int> used_chars;
  int               firstchar;
  int               lastchar;
  int               b0, b1, b2, b3; // glyph bounding box
  bool              first_glyph;

  t3font_rep (font_glyphs _fn, int _font_chunk, ObjectsContext& _objectsContext)
      : fn (_fn), font_chunk (_font_chunk), objectsContext (_objectsContext),
        first_glyph (true) {
    fontId= objectsContext.GetInDirectObjectsRegistry ().AllocateNewObjectID ();
  }
  void update_bbox (int llx, int lly, int urx, int ury);
  void add_glyph (int ch) { used_chars (ch)= 1; }
  void write_char (glyph gl, ObjectIDType inCharID);
  void write_definition (int& registry_id);
};

class t3font {
  CONCRETE_NULL (t3font);
  t3font (font_glyphs _fn, int _font_chunk, ObjectsContext& _objectsContext)
      : rep (tm_new<t3font_rep> (_fn, _font_chunk, _objectsContext)){};
};

CONCRETE_NULL_CODE (t3font);

void
t3font_rep::update_bbox (int llx, int lly, int urx, int ury) {
  if (first_glyph) {
    b0         = llx;
    b1         = lly;
    b2         = urx;
    b3         = ury;
    first_glyph= false;
  }
  else {
    if (llx < b0) b0= llx;
    if (lly < b1) b1= lly;
    if (urx > b2) b2= urx;
    if (ury > b3) b3= ury;
  }
}

void
t3font_rep::write_char (glyph gl, ObjectIDType inCharID) {
  int llx, lly, urx, ury, cwidth, cheight, lwidth;
  llx    = -gl->xoff;
  lly    = gl->yoff - gl->height + 1;
  urx    = gl->width - gl->xoff + 1;
  ury    = gl->yoff + 1;
  cwidth = gl->width;
  cheight= gl->height;
  lwidth = gl->lwidth;

  objectsContext.StartNewIndirectObject (inCharID);
  // write char stream
  PDFStream* charStream= objectsContext.StartPDFStream (NULL, true);
  string     data;
  if (is_nil (gl)) {
    // write d0 command
    data << "0 0 d0\r\n";
  }
  else {
    update_bbox (llx, lly, urx, ury);
    data << as_string (lwidth) << " 0 ";
    data << as_string (llx) << " " << as_string (lly) << " " << as_string (urx)
         << " " << as_string (ury) << " d1\r\n";
    data << "q\r\n";
    data << as_string ((double) (cwidth)) << " 0 0 "
         << as_string ((double) (cheight)) << " " << as_string ((double) (llx))
         << " " << as_string ((double) (lly)) << " cm\r\n";
    data << "BI\r\n/W " << as_string (cwidth) << "\r\n/H "
         << as_string (cheight) << "\r\n";
    data << "/CS /G /BPC 1 /F /AHx /D [0.0 1.0] /IM true\r\nID\r\n";
    static const char* hex_string= "0123456789ABCDEF";
    string             hex_code;
    int                i, j, count= 0, cur= 0;
    for (j= 0; j < cheight; j++)
      for (i= 0; i < ((cwidth + 7) & (-8)); i++) {
        cur= cur << 1;
        if ((i < cwidth) && (gl->get_x (i, j) == 0)) cur++;
        count++;
        if (count == 4) {
          hex_code << hex_string[cur];
          cur  = 0;
          count= 0;
        }
      }
    data << hex_code;
    data << ">\r\nEI\r\nQ\r\n"; // ">" is the EOD char for ASCIIHex
  }
  c_string buf (data);
  charStream->GetWriteStream ()->Write ((unsigned char*) (char*) buf, N (data));
  objectsContext.EndPDFStream (charStream); // It does the EndIndirectObject()
  delete charStream;
}

void
t3font_rep::write_definition (int& registry_id) {
  array<int>          glyph_list;
  array<ObjectIDType> charIds;
  // order used glyphs
  iterator<int> it= iterate (used_chars);
  while (it->busy ())
    glyph_list << t3font_get_local_glyph (it->next (), font_chunk,
                                          fn->res_name);
  merge_sort (glyph_list);
  if (N (glyph_list) > 0) {
    firstchar= glyph_list[0];
    lastchar = glyph_list[N (glyph_list) - 1];
  }
  else {
    convert_warning << "pdf_hummus_renderer, unexpected empty t3 font "
                    << fn->res_name << LF;
    firstchar= 255;
    lastchar = 0;
  }
  // write glyphs definitions
  for (int i= 0; i < N (glyph_list); ++i) {
    int   ch= t3font_get_global_glyph (glyph_list[i], font_chunk, fn->res_name);
    glyph gl= fn->get (ch);
    ObjectIDType temp=
        objectsContext.GetInDirectObjectsRegistry ().AllocateNewObjectID ();
    charIds << temp;
    write_char (gl, temp);
  }
  ObjectIDType tounicodeId;
  // create font dictionary
  string dict;
  dict << "<<\r\n";
  dict << "\t/Type /Font\r\n";
  dict << "\t/Resources << /ProcSet [/PDF /ImageB] >>\r\n";
  dict << "\t/Subtype /Type3\r\n\t/FontBBox [ " << as_string (b0) << " "
       << as_string (b1) << " " << as_string (b2) << " " << as_string (b3)
       << "]\r\n";
  dict << "\t/FontMatrix [" << as_string (1 / 100.0) << " 0 0 "
       << as_string (1 / 100.0) << " 0 0 ]\r\n";
  dict << "\t/FirstChar " << as_string (firstchar) << "\r\n\t/LastChar "
       << as_string (lastchar) << "\r\n";
  dict << "\t/Widths [ ";
  if (N (glyph_list) > 0)
    for (int i= firstchar; i <= lastchar; ++i) {
      int ch= t3font_get_global_glyph (i, font_chunk, fn->res_name);
      if (used_chars->contains (ch))
        dict << as_string ((double) (fn->get (ch)->lwidth)) << " ";
      else dict << "0 ";
    }
  // Write CharProcs
  dict << "]\r\n\t/CharProcs <<\r\n";
  for (int i= 0; i < N (glyph_list); ++i) {
    int          ch  = glyph_list[i];
    ObjectIDType temp= charIds[i];
    dict << "\t\t/ch" << as_string (ch) << " " << as_string (temp)
         << " 0 R\r\n";
  }
  if (N (glyph_list) > 0)
    dict << "\t\t/.notdef " << as_string (charIds[0]) << " 0 R\r\n";
  dict << "\t>>\r\n";
  // Encodings
  if (N (glyph_list) > 0) {
    tounicodeId=
        objectsContext.GetInDirectObjectsRegistry ().AllocateNewObjectID ();
    dict << "\t/ToUnicode " << as_string (tounicodeId) << " 0 R\r\n";
  }
  dict << "\t/Encoding <<\r\n";
  dict << "\t\t/Type /Encoding\r\n";
  dict << "\t\t/Differences [";
  if (N (glyph_list) > 0)
    for (int i= firstchar, previousEncoding= firstchar; i <= lastchar; ++i) {
      int ch= t3font_get_global_glyph (i, font_chunk, fn->res_name);
      if (used_chars->contains (ch)) {
        if (previousEncoding + 1 != i) {
          dict << "\r\n\t\t\t" << as_string (i) << " ";
        }
        dict << "/ch" << as_string (i) << " ";
        previousEncoding= i;
      }
    }
  dict << " ]\r\n\t>>\r\n>>\r\n";
  write_indirect_obj (objectsContext, fontId, dict);
  if (N (glyph_list) == 0) return;
  // Cmap
  registry_id++;
  string registry= "TeXmacs-type3-ToUnicode-CMap-" * as_string (registry_id);
  string cmap;
  cmap << "/CIDInit /ProcSet findresource begin 12 dict\r\n"
       << "begin begincmap /CIDSystemInfo <<\r\n"
       << "  /Registry (" << registry
       << ") /Ordering (Custom) /Supplement 0 >>\r\n"
       << "def /CMapName /" << registry << " def /CMapType 2 def\r\n";
  cmap << "1 begincodespacerange <" << as_hexadecimal (firstchar, 2) << "> <"
       << as_hexadecimal (lastchar, 2) << "> endcodespacerange\r\n";
#if 1
  cmap << "1 beginbfrange\r\n";
  int ch= t3font_get_global_glyph (firstchar, font_chunk, fn->res_name);
  cmap << "\t<" << as_hexadecimal (firstchar, 2) << "> "
       << "<" << as_hexadecimal (lastchar, 2) << "> "
       << "<" << as_hexadecimal (ch, 4) << ">\r\n";
  cmap << "endbfrange\r\n";
#else
  // Left for testing purpose
  cmap << as_string (N (glyph_list)) << " beginbfchar\r\n";
  for (int i= 0; i < N (glyph_list); i++) {
    int lch= glyph_list[i];
    int ch = t3font_get_global_glyph (lch, font_chunk, fn->res_name);
    cmap << "\t<" << as_hexadecimal (lch, 2) << "> <" << as_hexadecimal (ch, 4)
         << ">\r\n";
  }
  cmap << "endbfchar\r\n";
#endif
  cmap << "endcmap CMapName currentdict /CMap defineresource\r\n"
       << "pop end end\r\n";
  objectsContext.StartNewIndirectObject (tounicodeId);
  PDFStream*           cmapStream= objectsContext.StartPDFStream (NULL, true);
  OutputStreamTraits   outputTraits (cmapStream->GetWriteStream ());
  c_string             buf (cmap);
  InputByteArrayStream reader ((IOBasicTypes::Byte*) (char*) buf, N (cmap));
  EStatusCode          status= outputTraits.CopyToOutputStream (&reader);
  if (status != PDFHummus::eSuccess) {
    delete cmapStream;
    convert_error << "pdf_hummus_renderer, internal error at line "
                  << as_string (__LINE__) << LF;
  }
  objectsContext.EndPDFStream (cmapStream); // It does EndIndirectObject();
  delete cmapStream;
}

void
pdf_hummus_renderer_rep::flush_fonts () {
  // flush t3 fonts
  iterator<string> it= iterate (t3font_list);
  while (it->busy ()) {
    string name= it->next ();
    t3font f   = t3font_list[name];
    f->write_definition (t3font_registry_id);
  }
}

/******************************************************************************
 * Text handling
 ******************************************************************************/

static hashset<string>
pdf_font_issues () {
  static const url       u ("$TEXMACS_PATH/fonts/pdf-font-issues.scm");
  static bool            loaded= false;
  static hashset<string> set;
  if (loaded) return set;
  string s;
  if (exists (u) && !load_string (u, s, false)) {
    tree t= block_to_scheme_tree (s);
    for (int i= 0; i < N (t); i++)
      if (is_func (t[i], TUPLE) && (N (t[i]) >= 1)) {
        tree key= t[i][0];
        if (is_string (key)) set->insert (as_string (key));
      }
  }
  loaded= true;
  return set;
}

static bool
no_font_issues (url u) {
  string h= as_standard_string (tail (u));
  return !pdf_font_issues ()->contains (h);
}

void
pdf_hummus_renderer_rep::make_pdf_font (string fontname) {
  int               pos          = search_forwards (":", fontname);
  string            fname        = (pos == -1 ? fontname : fontname (0, pos));
  pair<string, int> f_pair       = font_name_unpack (fname);
  string            font_basename= f_pair.x1;
  int               face_index   = f_pair.x2;
  url               u            = tt_font_find (font_basename);
  if (!is_none (u)) {
    PDFUsedFont* font;
    {
      c_string _u (concretize (u));
      font= pdfWriter.GetFontForFile ((char*) _u, face_index);
    }

    if (font != NULL && no_font_issues (u)) {
      native_fonts (fontname)= font;
      std::string _ps_name   = font->GetFreeTypeFont ()->GetPostscriptName ();
      string      ps_name (_ps_name.c_str ());
      if (starts (ps_name, "EuropeanComputerModern"))
        EuropeanComputerModern_fonts->insert (fontname);
      return;
    }
    else {
      convert_warning << "pdf_hummus_renderer, font: " << fname << " in file "
                      << u << " cannot be loaded. "
                      << "It is converted to bitmap type 3 font." << LF;
    }
  }
  not_native_fonts->insert (fontname);
}

static double
font_size (string name) {
  int pos  = search_backwards (".", name);
  int szpos= pos - 1;
  while ((szpos > 0) && is_numeric (name[szpos - 1]))
    szpos--;
  double size= as_double (name (szpos, pos));
  if (size == 0) size= 10;
  int end= pos + 1;
  while (end < N (name) && is_numeric (name[end]))
    end++;
  double dpi= as_double (name (pos + 1, end));
  double mag= (size) * (dpi / 72.0);
  return mag;
}

static bool
match_font_base_name (string fontname, string basename) {
  if (!starts (fontname, basename)) return false;
  int i= N (basename);
  while (i < N (fontname)) {
    if (fontname[i] == ':') break;
    if (fontname[i] < '0' || fontname[i] > '9') return false;
    i++;
  }
  return true;
}

static bool
requires_hack_notdef_for_tex_font (string fontname) {
  // This fix is necessary for avoiding bugs in certain Pdf viewers,
  // such as old versions of Preview under MacOS (<= 10.6.*).
  // It is expected to be removed in near future.
  static hashmap<string, bool> remember;
  if (remember->contains (fontname)) return remember[fontname];
  bool r= match_font_base_name (fontname, "cmbsy") ||
          match_font_base_name (fontname, "cmmib") ||
          match_font_base_name (fontname, "cmb") ||
          match_font_base_name (fontname, "cmbx") ||
          match_font_base_name (fontname, "cmbxsl") ||
          match_font_base_name (fontname, "cmbxti") ||
          match_font_base_name (fontname, "cmbcsc") ||
          match_font_base_name (fontname, "cmdunh") ||
          match_font_base_name (fontname, "cmex") ||
          match_font_base_name (fontname, "cmexb") ||
          match_font_base_name (fontname, "cmff") ||
          match_font_base_name (fontname, "cmfi") ||
          match_font_base_name (fontname, "cmfib") ||
          match_font_base_name (fontname, "cminch") ||
          match_font_base_name (fontname, "cmitt") ||
          match_font_base_name (fontname, "cmmi") ||
          match_font_base_name (fontname, "cmmib") ||
          match_font_base_name (fontname, "cmr") ||
          match_font_base_name (fontname, "cmsl") ||
          match_font_base_name (fontname, "cmsltt") ||
          match_font_base_name (fontname, "cmss") ||
          match_font_base_name (fontname, "cmssbx") ||
          match_font_base_name (fontname, "cmssdc") ||
          match_font_base_name (fontname, "cmssi") ||
          match_font_base_name (fontname, "cmssq") ||
          match_font_base_name (fontname, "cmssqi") ||
          match_font_base_name (fontname, "cmsy") ||
          match_font_base_name (fontname, "cmtcsc") ||
          match_font_base_name (fontname, "cmtex") ||
          match_font_base_name (fontname, "cmti") ||
          match_font_base_name (fontname, "cmtt") ||
          match_font_base_name (fontname, "cmu") ||
          match_font_base_name (fontname, "cmvtt") ||
          match_font_base_name (fontname, "euex") ||
          match_font_base_name (fontname, "eufb") ||
          match_font_base_name (fontname, "eufm") ||
          match_font_base_name (fontname, "eurb") ||
          match_font_base_name (fontname, "eurm") ||
          match_font_base_name (fontname, "eusb") ||
          match_font_base_name (fontname, "eusm") ||
          match_font_base_name (fontname, "msam") ||
          match_font_base_name (fontname, "msbm");
  remember (fontname)= r;
  return r;
}

void
pdf_hummus_renderer_rep::draw (int ch, font_glyphs fn, SI x, SI y) {
  // debug_convert << "draw \"" << (char)ch << "\" " << ch << " "
  //		<< fn->res_name << "\n";
  glyph gl= fn->get (ch);
  if (is_nil (gl)) return;
  string fontname     = fn->res_name;
  int    fontchunk    = t3font_font_chunk (ch);
  string fontchunkname= fontname * string ("-chunk") * as_string (fontchunk);

  if (ch == 0 && requires_hack_notdef_for_tex_font (fontname)) {
    draw (161, fn, x, y);
    return;
  }
  string        char_name (fontname * "-" * as_string (ch));
  pdf_raw_image glyph;

  if (cfn != fontname && cfn != fontchunkname) {
    if (!native_fonts->contains (fontname) &&
        !not_native_fonts->contains (fontname))
      make_pdf_font (fontname);
    if (not_native_fonts->contains (fontname)) {
      fontname= fontchunkname;
      if (!t3font_list->contains (fontname)) {
        // cout << "create t3font for chunk " << fontchunk
        //      << " of " << fn->res_name << LF;
        t3font f (fn, fontchunk, pdfWriter.GetObjectsContext ());
        t3font_list (fontchunkname)= f;
      }
    }
    // debug_convert << "CHANGE FONT" << LF;
    begin_text ();
    fsize= font_size (fontname);
    if (native_fonts->contains (fontname)) {
      cfid= native_fonts (fontname);
      contentContext->Tf (cfid, fsize);
    }
    else {
      cfid            = NULL;
      std::string name= page->GetResourcesDictionary ().AddFontMapping (
          t3font_list (fontname)->fontId);
      // pk fonts are encoded in t3 fonts as bitmaps.
      // they cannot be scaled and are encoded in such a way that
      // they should be rendered at size 100 (conventional value)
      // to give the correct result (see the Font Matrix defined
      // in t3font_rep::write_definition).
      contentContext->TfLow (name, 100);
    }
    cfn= fontname;
  }
  else cfid= native_fonts (fontname);
  begin_text ();
  contentContext->Td (to_x (x) - prev_text_x, to_y (y) - prev_text_y);
  prev_text_x= to_x (x);
  prev_text_y= to_y (y);
  // debug_convert << "char " << ch << "index " << gl->index
  //               << " " << x << " " << y << " font " << cfn  << LF;
  if (cfid == NULL) t3font_list (cfn)->add_glyph (ch);
  GlyphUnicodeMappingList glyphs;
  if (cfid != NULL && EuropeanComputerModern_fonts->contains (cfn) &&
      gl->index >= 27 && gl->index <= 31) {
    ch+= 0xfb00 - 27;
  }
  int gl_index;
  if (cfid != NULL) gl_index= gl->index;
  else
    gl_index= t3font_get_local_glyph (ch, t3font_list (cfn)->font_chunk,
                                      t3font_list (cfn)->fn->res_name);
  static const std::string ligature_ff = "/Span << /ActualText (ff) >> BDC ";
  static const std::string ligature_fi = "/Span << /ActualText (fi) >> BDC ";
  static const std::string ligature_fl = "/Span << /ActualText (fl) >> BDC ";
  static const std::string ligature_ffi= "/Span << /ActualText (ffi) >> BDC ";
  static const std::string ligature_ffl= "/Span << /ActualText (ffl) >> BDC ";
  if (ch >= 0xfb00 && ch <= 0xfb04 && ePDFVersion >= ePDFVersion15) {
    if (ch == 0xfb00) contentContext->WriteFreeCode (ligature_ff);
    if (ch == 0xfb01) contentContext->WriteFreeCode (ligature_fi);
    if (ch == 0xfb02) contentContext->WriteFreeCode (ligature_fl);
    if (ch == 0xfb03) contentContext->WriteFreeCode (ligature_ffi);
    if (ch == 0xfb04) contentContext->WriteFreeCode (ligature_ffl);
    if (cfid != NULL) {
      glyphs.push_back (GlyphUnicodeMapping (gl_index, ch));
      contentContext->Tj (glyphs);
    }
    else {
      std::string buf;
      buf.push_back (gl_index);
      contentContext->TjLow (buf);
    }
    contentContext->WriteFreeCode (" EMC\r\n");
  }
  else {
    if (cfid != NULL) {
      glyphs.push_back (GlyphUnicodeMapping (gl_index, ch));
      contentContext->Tj (glyphs);
    }
    else {
      std::string buf;
      buf.push_back (gl_index);
      contentContext->TjLow (buf);
    }
  }
}

/******************************************************************************
 * Graphics primitives
 ******************************************************************************/

void
pdf_hummus_renderer_rep::line (SI x1, SI y1, SI x2, SI y2) {
  // debug_convert << "line\n";
  end_text ();
  contentContext->m (to_x (x1), to_y (y1));
  contentContext->l (to_x (x2), to_y (y2));
  contentContext->S ();
}

void
pdf_hummus_renderer_rep::lines (array<SI> x, array<SI> y) {
  // debug_convert << "lines\n";
  end_text ();
  int i, n= N (x);
  if ((N (y) != n) || (n < 1)) return;
  end_text ();
  contentContext->q ();
  if (pen->get_cap () == cap_round ||
      (x[N (x) - 1] == x[0] && y[N (y) - 1] == y[0]))
    contentContext->J (1);    // round cap
  else contentContext->J (2); // square cap
  contentContext->j (1);      // round join
  contentContext->m (to_x (x[0]), to_y (y[0]));
  for (i= 1; i < n; i++) {
    contentContext->l (to_x (x[i]), to_y (y[i]));
  }
  contentContext->S ();
  contentContext->Q ();
}

void
pdf_hummus_renderer_rep::clear (SI x1, SI y1, SI x2, SI y2) {
  end_text ();
  double xx1= to_x (min (x1, x2));
  double yy1= to_y (min (y1, y2));
  double xx2= to_x (max (x1, x2));
  double yy2= to_y (max (y1, y2));
  // debug_convert << "clear" << xx1 << " " << yy1 << " " << xx2 << " " << yy2
  // << LF;
  contentContext->q ();
  select_fill_color (bg);
  select_fill_pattern (bgb);
  contentContext->re (xx1, yy1, xx2 - xx1, yy2 - yy1);
  contentContext->h ();
  contentContext->f ();
  select_fill_color (fg);
  select_fill_pattern (fgb);
  contentContext->Q ();
}

void
pdf_hummus_renderer_rep::fill (SI x1, SI y1, SI x2, SI y2) {
  if ((x1 < x2) && (y1 < y2)) {
    end_text ();
    double xx1= to_x (min (x1, x2));
    double yy1= to_y (min (y1, y2));
    double xx2= to_x (max (x1, x2));
    double yy2= to_y (max (y1, y2));
    contentContext->re (xx1, yy1, xx2 - xx1, yy2 - yy1);
    contentContext->h ();
    contentContext->f (); // FIXME Winding
  }
}

void
pdf_hummus_renderer_rep::bezier_arc (SI x1, SI y1, SI x2, SI y2, int alpha,
                                     int delta, bool filled) {
  // PDF can describe only cubic bezier paths, so we have to make up the arc
  // with them Since this is not mathematically exact, we minimize errors by
  // drawing beziers sub-arcs of at most 90??
  end_text ();
  contentContext->q (); // save graphics state

  double xx1= to_x (x1), yy1= to_y (y1), xx2= to_x (x2), yy2= to_y (y2);
  double cx= (xx1 + xx2) / 2, cy= (yy1 + yy2) / 2;
  double rx= (xx2 - xx1) / 2, ry= (yy2 - yy1) / 2;
  contentContext->cm (1, 0, 0, 1, cx, cy); // centering
  // we can't apply scale here because in pdf the pen is scaled too

  int i= 1 + abs (delta) / (90 * 64); // number of sub-arcs needed
  if ((abs (delta) % (90 * 64)) == 0)
    i--; // correction needed if exact multiple of 90??
  double phi=
      2.0 * M_PI * (delta) / (i * 360.0 * 64.0); // angular span of each sub-arc
  double a= 2.0 * M_PI * (alpha) / (360.0 * 64.0); // start angle in radians

  // Control points for an arc of radius 1, centered on the x-axis and spanning
  // phi degrees from: http://www.tinaja.com/glib/bezcirc2.pdf
  double sphi= sin (phi / 2), cphi= cos (phi / 2);
  double bx0= cphi, by0= -sphi;
  double bx1= (4.0 - bx0) / 3.0, by1= (1.0 - bx0) * (3.0 - bx0) / (3.0 * by0);
  double bx2= bx1, by2= -by1;
  double bx3= bx0, by3= -by0;

  // repeatedly draw rotated and scaled sub-arc
  // cannot use user-space transformations with cm util path is painted
  // (otherwise path is lost) so we perform explicit rotation+scaling
  // calculations
  int k;
  for (k= 0; k < i; k++) {
    sphi= sin (phi * (k + 0.5) + a);
    cphi= cos (phi * (k + 0.5) + a);
    if (k == 0)
      contentContext->m (rx * (bx0 * cphi - by0 * sphi),
                         ry * (+bx0 * sphi + by0 * cphi)); // start point
    contentContext->c (
        rx * (bx1 * cphi - by1 * sphi), ry * (+bx1 * sphi + by1 * cphi),
        rx * (bx2 * cphi - by2 * sphi), ry * (+bx2 * sphi + by2 * cphi),
        rx * (bx3 * cphi - by3 * sphi), ry * (+bx3 * sphi + by3 * cphi));
  }

  // paint
  if (filled) {
    // contentContext->l(0.0, 0.0); // for a filled "pie" with vertex at the
    // center
    contentContext->f ();
  }
  else
    ((abs (delta) == 360 * 64) ? contentContext->s () : contentContext->S ());
  // here we close the path if it's a full circle
  contentContext->Q (); // restore the graphics state (undoes centering only)
}

void
pdf_hummus_renderer_rep::arc (SI x1, SI y1, SI x2, SI y2, int alpha,
                              int delta) {
  // debug_convert << "arc\n";
  end_text ();
  bezier_arc (x1, y1, x2, y2, alpha, delta, false);
}

void
pdf_hummus_renderer_rep::fill_arc (SI x1, SI y1, SI x2, SI y2, int alpha,
                                   int delta) {
  // debug_convert << "fill_arc\n";
  end_text ();
  bezier_arc (x1, y1, x2, y2, alpha, delta, true);
}

void
pdf_hummus_renderer_rep::polygon (array<SI> x, array<SI> y, bool convex) {
  // debug_convert << "polygon\n";
  int i, n= N (x);
  if ((N (y) != n) || (n < 1)) return;
  end_text ();
  contentContext->m (to_x (x[0]), to_y (y[0]));
  for (i= 1; i < n; i++)
    contentContext->l (to_x (x[i]), to_y (y[i]));
  contentContext->h ();
  if (convex) contentContext->f (); // odd-even
  else contentContext->fStar ();    // nonzero winding
}

void
pdf_hummus_renderer_rep::flush_images () {
  // flush all images
  iterator<tree> it= iterate (image_pool);
  while (it->busy ()) {
    pdf_image im= image_pool[it->next ()];
    im->flush (pdfWriter);
  }
}

void
pdf_hummus_renderer_rep::flush_patterns () {
  // flush all pattern images
  iterator<tree> it= iterate (pattern_image_pool);
  while (it->busy ()) {
    pdf_image im= pattern_image_pool[it->next ()];
    im->flush_for_pattern (pdfWriter);
  }
  // flush all patterns
  it= iterate (pattern_pool);
  while (it->busy ()) {
    pdf_pattern pa= pattern_pool[it->next ()];
    pa->flush (pdfWriter);
  }
}

void
pdf_hummus_renderer_rep::image (url u, double w, double h, SI x, SI y,
                                int alpha) {
  // debug_convert << "pdf renderer, image " << u << ", " << w << " x " << h
  //		<< " + (" << x << ", " << y << ")" << LF;
  tree      lookup= tuple (as_tree (u));
  pdf_image im=
      (image_pool->contains (lookup) ? image_pool[lookup] : pdf_image ());

  if (is_nil (im)) {
    im                 = pdf_image (u, pdfWriter.GetObjectsContext ()
                                           .GetInDirectObjectsRegistry ()
                                           .AllocateNewObjectID ());
    image_pool (lookup)= im;
  }

  if (is_nil (im)) return;

  end_text ();

  contentContext->q ();
  std::string initial_GState_name=
      page->GetResourcesDictionary ().AddExtGStateMapping (initial_GState_id);
  contentContext->gs (initial_GState_name);
  contentContext->cm (((double) w) / ((double) im->w), 0, 0,
                      ((double) h) / ((double) im->h), to_x (x), to_y (y));
  std::string pdfFormName=
      page->GetResourcesDictionary ().AddFormXObjectMapping (im->id);
  select_alpha ((1000 * alpha) / 255);
  contentContext->Do (pdfFormName);
  // contentContext->re(0,0,im->w,im->h);
  // contentContext->S();
  contentContext->Q ();
}

/******************************************************************************
 * Applying effects to existing pictures
 ******************************************************************************/

#ifdef QTTEXMACS
static picture
pdf_raster_picture (picture pic) {
  picture ret= qt_picture (
      QImage (pic->get_width (), pic->get_height (), QImage::Format_ARGB32),
      pic->get_origin_x (), pic->get_origin_y ());
  ret->copy_from (pic);
  return ret;
}
#endif

void
pdf_hummus_renderer_rep::draw_picture (picture p, SI x, SI y, int alpha) {
  // debug_convert << "pdf renderer, draw_picture " << x << ", " << y
  //		<< " (" << alpha << ")" << LF;
  url                    temp;
  unsigned long long int key= p->get_unique_id ();
  if (picture_cache->contains (key)) temp= picture_cache[key];
  else {
    // As an improvement one could handle native pictures without conversions
#ifdef QTTEXMACS
    picture         q   = pdf_raster_picture (p);
    qt_picture_rep* pict= (qt_picture_rep*) q->get_handle ();
    temp                = url_temp ("png");
    pict->pict.save (utf8_to_qstring (concretize (temp)), "PNG");
    temp_images << temp;
#else
    convert_error << "pdf renderer, draw_picture: "
                  << "cannot export picture " << p->get_name () << LF;
#endif
    picture_cache (key)= temp;
  }
  int _pixel= (int) (PIXEL / PICTURE_ZOOM);
  int w= p->get_width (), h= p->get_height ();
  int ox= p->get_origin_x (), oy= p->get_origin_y ();
  image (temp, w, h, x - ox * _pixel, y - oy * _pixel, alpha);
}

void
pdf_hummus_renderer_rep::draw_scalable (scalable im, SI x, SI y, int alpha) {
  // debug_convert << "pdf renderer, draw_scalable "
  //   << im->get_name () << " at " << x << ", " << y
  //   << " (" << alpha << ")" << LF;
  if (im->get_type () != scalable_image ||
      (im->get_type () == scalable_image && im->get_effect () != tree ("")))
    renderer_rep::draw_scalable (im, x, y, alpha);
  else {
    url       u= im->get_name ();
    rectangle r= im->get_logical_extents ();
    SI        w= r->x2 - r->x1, h= r->y2 - r->y1;
    int       ox= r->x1, oy= r->y1;
    image (u, ((double) w) / pixel, ((double) h) / pixel, x - ox, y - oy,
           alpha);
  }
}

/******************************************************************************
 * Conversions between strings and std::string
 ******************************************************************************/

string
std_string_to_string (std::string str) {
  string r;
  for (std::string::iterator it= str.begin (); it != str.end (); ++it) {
    r << *it;
  }
  return r;
}

// At least in outlines a single paren will break the PDF and no more
// outlines will show up after it. Does it need this for other PDF
// strings?
//
// Jim King, Document Management — Portable Document Format — Part 1:
// PDF 1.7 (Adobe Sys. Inc., First ed. 2008),
// §7.3.4.2, Literal Strings, Table 3, Escape sequences in literal strings
//
// Fixme? This does not handle octal character sequences \nnn. Does it
// ever need to?
//
string
escape_string (string s) {
  int    i, n= N (s);
  string r;
  for (i= 0; i < n; i++)
    switch (s[i]) {
    case '\n':
      r << '\\' << 'n';
      break;
    case '\r':
      r << '\\' << 'r';
      break;
    case '\t':
      r << '\\' << 't';
      break;
    case '\b':
      r << '\\' << 'b';
      break;
    case '\f':
      r << '\\' << 'f';
      break;
    case '(':
    case ')':
    case '\\':
      r << '\\' << s[i];
      break;
    default:
      r << s[i];
    }
  return r;
}

static PDFTextString
utf8_as_hummus_string (string s) {
  c_string      u (s);
  PDFTextString r;
  std::string   stds ((char*) u);
  return r.FromUTF8 (stds);
}

/******************************************************************************
 * hyperlinks
 ******************************************************************************/

PDFHummus::EStatusCode
pdf_hummus_renderer_rep::on_catalog_write (
    CatalogInformation*         inCatalogInformation,
    DictionaryContext*          inCatalogDictionaryContext,
    ObjectsContext*             inPDFWriterObjectContext,
    PDFHummus::DocumentContext* inDocumentContext) {
  (void) inCatalogInformation;
  (void) inPDFWriterObjectContext;
  (void) inDocumentContext;
  if (destId) {
    inCatalogDictionaryContext->WriteKey ("Dests");
    inCatalogDictionaryContext->WriteNewObjectReferenceValue (destId);
  }
  if (outlineId) {
    inCatalogDictionaryContext->WriteKey ("Outlines");
    inCatalogDictionaryContext->WriteNewObjectReferenceValue (outlineId);
  }
  return PDFHummus::eSuccess;
}

int
pdf_hummus_renderer_rep::get_label_id (string label) {
  if (!(label_id->contains (label))) {
    label_id (label)= label_count;
    label_count++;
  }
  return label_id (label);
}

void
pdf_hummus_renderer_rep::anchor (string label, SI x1, SI y1, SI x2, SI y2) {
  (void) x2;
  (void) y1;
  dests << dest_data (label, page_num, to_x (x1), to_y (y2 + 20 * pixel));
}

void
pdf_hummus_renderer_rep::href (string label, SI x1, SI y1, SI x2, SI y2) {
  bool         preserve= (get_locus_rendering ("locus-on-paper") == "preserve");
  ObjectIDType annotId = pdfWriter.GetObjectsContext ()
                            .GetInDirectObjectsRegistry ()
                            .AllocateNewObjectID ();
  pdfWriter.GetDocumentContext ().RegisterAnnotationReferenceForNextPageWrite (
      annotId);
  string dict;
  dict << "<<\r\n\t/Type /Annot\r\n\t/Subtype /Link\r\n";
  //  dict << "\t/Border [1.92 1.92 0.12[]]\r\n\t/Color [0.75 0.5 1.0]\r\n";
  if (preserve) dict << "\t/Border [16 16 1 [3 10]] /Color [0.75 0.5 1.0]\r\n";
  else dict << "\t/Border [16 16 0 [3 10]] /Color [0.75 0.5 1.0]\r\n";
  dict << "\t/Rect ["
       << as_string (((double) default_dpi / dpi) * to_x (x1 - 5 * pixel))
       << " ";
  dict << as_string (((double) default_dpi / dpi) * to_y (y1 - 10 * pixel))
       << " ";
  dict << as_string (((double) default_dpi / dpi) * to_x (x2 + 5 * pixel))
       << " ";
  dict << as_string (((double) default_dpi / dpi) * to_y (y2 + 10 * pixel))
       << "]\r\n";
  if (starts (label, "#")) {
    dict << "\t/Dest /label" << as_string (get_label_id (label)) << "\r\n";
  }
  else {
    dict << "/A << /S /URI /URI (" << label << ") >>\r\n";
  }
  dict << ">>\r\n";
  annot_list (annotId)= dict;
}

void
pdf_hummus_renderer_rep::flush_dests () {
  if (is_nil (dests)) return;

  // flush destinations

  string dict;
  dict << "<<\r\n";
  list<dest_data> it= dests;
  while (!is_nil (it)) {
    string label    = it->item.x1;
    int    dest_page= it->item.x2;
    int    dest_x   = it->item.x3;
    int    dest_y   = it->item.x4;
    {
      dict << "\t\t/label" << as_string (get_label_id (label)) << " [ "
           << as_string (page_id (dest_page)) << " 0 R /XYZ "
           << as_string (((double) default_dpi / dpi) * dest_x) << " "
           << as_string (((double) default_dpi / dpi) * dest_y)
           << " null ]\r\n";
    }
    it= it->next;
  }
  dict << ">>\r\n";
  {
    // flush the buffer
    ObjectsContext& objectsContext= pdfWriter.GetObjectsContext ();
    destId= objectsContext.GetInDirectObjectsRegistry ().AllocateNewObjectID ();
    write_indirect_obj (objectsContext, destId, dict);
  }
}

void
pdf_hummus_renderer_rep::toc_entry (string kind, string title, SI x, SI y) {
  (void) kind;
  (void) title;
  (void) x;
  (void) y;
  // debug_convert << kind << ", " << title << "\n";
  int ls= 1;
  if (kind == "toc-strong-1") ls= 1;
  if (kind == "toc-strong-2") ls= 2;
  if (kind == "toc-1") ls= 3;
  if (kind == "toc-2") ls= 4;
  if (kind == "toc-3") ls= 5;
  if (kind == "toc-4") ls= 6;
  if (kind == "toc-5") ls= 7;

  // Use escape_string here. Should it be part of prepare_text instead?
  outlines << outline_data (escape_string (title), page_num, to_x (x),
                            to_y (y + 20 * pixel), ls);
}

void
pdf_hummus_renderer_rep::recurse (ObjectsContext& objectsContext, int parent_ls,
                                  list<outline_data>& it, ObjectIDType parentId,
                                  ObjectIDType& firstId, ObjectIDType& lastId,
                                  int& count) {
  // weave the tangle of forward/backward references
  // are recurse over substructures

  ObjectIDType prevId= 0, curId= 0, nextId= 0;

  if (!is_nil (it))
    curId= objectsContext.GetInDirectObjectsRegistry ().AllocateNewObjectID ();

  firstId= curId;
  while (curId) {
    ObjectIDType subFirstId= 0, subLastId= 0;
    int          subCount= 0;

    outline_data oitem= it->item;
    count++;
    it= it->next;

    if (!is_nil (it) && ((it->item).x5 > oitem.x5)) {
      // go down in level
      recurse (objectsContext, oitem.x5, it, curId, subFirstId, subLastId,
               subCount);
    }

    // continue at this level or above
    if (!is_nil (it) && ((it->item).x5 <= oitem.x5) &&
        ((it->item).x5 > parent_ls)) {
      nextId=
          objectsContext.GetInDirectObjectsRegistry ().AllocateNewObjectID ();
    }
    else {
      // finished this level, go up.
      nextId= 0;
    }

    {
      // write current outline item dictionary
      string dict;
      dict << "<<\r\n"
           << "\t/Title " << utf8_to_pdf_hex_string ((oitem).x1) << "\r\n"
           << "\t/Parent " << as_string (parentId) << " 0 R \r\n";
      if (prevId != 0) dict << "\t/Prev " << as_string (prevId) << " 0 R \r\n";
      if (nextId != 0) dict << "\t/Next " << as_string (nextId) << " 0 R \r\n";
      if (subCount > 0) {
        dict << "\t/First " << as_string (subFirstId) << " 0 R \r\n"
             << "\t/Last " << as_string (subLastId) << " 0 R \r\n"
             << "\t/Count " << as_string (-subCount) << "\r\n";
      }
      dict << "\t/Dest [ " << as_string (page_id ((oitem).x2)) << " 0 R /XYZ "
           << as_string (((double) default_dpi / dpi) * ((oitem).x3)) << " "
           << as_string (((double) default_dpi / dpi) * ((oitem).x4))
           << " null ]\r\n"
           << ">>\r\n";
      write_indirect_obj (objectsContext, curId, dict);
    }
    prevId= curId;
    curId = nextId;
  }
  lastId= prevId;
}

void
pdf_hummus_renderer_rep::flush_outlines () {
  if (is_nil (outlines)) return;

  ObjectsContext&    objectsContext= pdfWriter.GetObjectsContext ();
  ObjectIDType       firstId= 0, lastId= 0;
  int                count= 0;
  list<outline_data> it   = outlines;

  outlineId=
      objectsContext.GetInDirectObjectsRegistry ().AllocateNewObjectID ();
  recurse (objectsContext, 0, it, outlineId, firstId, lastId, count);
  {
    // create top-level outline dictionary
    string dict;
    dict << "<<\r\n";
    dict << "\t/Type /Outlines\r\n";
    dict << "\t/First " << as_string (firstId) << " 0 R \r\n";
    dict << "\t/Last " << as_string (lastId) << " 0 R \r\n";
    dict << "\t/Count " << as_string (count) << "\r\n";
    dict << ">>\r\n";
    write_indirect_obj (objectsContext, outlineId, dict);
  }
}

/******************************************************************************
 * shadow rendering is trivial on pdf
 ******************************************************************************/

void
pdf_hummus_renderer_rep::set_metadata (string kind, string val) {
  metadata (kind)= val;
}

void
pdf_hummus_renderer_rep::flush_metadata () {
  if (N (metadata) == 0) return;
  DocumentContext&    documentContext= pdfWriter.GetDocumentContext ();
  TrailerInformation& trailerInfo    = documentContext.GetTrailerInformation ();
  InfoDictionary&     info           = trailerInfo.GetInfo ();
  if (metadata->contains ("title"))
    info.Title= utf8_as_hummus_string (metadata["title"]);
  if (metadata->contains ("author"))
    info.Author= utf8_as_hummus_string (metadata["author"]);
  if (metadata->contains ("subject"))
    info.Subject= utf8_as_hummus_string (metadata["subject"]);
  if (metadata->contains ("keyword"))
    info.Keywords= utf8_as_hummus_string (metadata["keyword"]);
  string creator = "Mogan " * string (XMACS_VERSION);
  string producer= creator * " + PDFHummus" * string (PDFHUMMUS_VERSION);
  info.Creator   = utf8_as_hummus_string (creator);
  info.Producer  = utf8_as_hummus_string (producer);
  PDFDate date;
  date.SetToCurrentTime ();
  info.CreationDate= date;
}

/******************************************************************************
 * shadow rendering is trivial on pdf
 ******************************************************************************/

void
pdf_hummus_renderer_rep::fetch (SI x1, SI y1, SI x2, SI y2, renderer ren, SI x,
                                SI y) {
  // debug_convert << "fetch\n";
  (void) x1;
  (void) y1;
  (void) x2;
  (void) y2;
  (void) ren;
  (void) x;
  (void) y;
}

void
pdf_hummus_renderer_rep::new_shadow (renderer& ren) {
  // debug_convert << "new_shadow\n";
  (void) ren;
}

void
pdf_hummus_renderer_rep::delete_shadow (renderer& ren) {
  // debug_convert << "delete_shadow\n";
  (void) ren;
}

void
pdf_hummus_renderer_rep::get_shadow (renderer ren, SI x1, SI y1, SI x2, SI y2) {
  // debug_convert << "get_shadow\n";
  (void) ren;
  (void) x1;
  (void) y1;
  (void) x2;
  (void) y2;
}

void
pdf_hummus_renderer_rep::put_shadow (renderer ren, SI x1, SI y1, SI x2, SI y2) {
  // debug_convert << "put_shadow\n";
  (void) ren;
  (void) x1;
  (void) y1;
  (void) x2;
  (void) y2;
}

void
pdf_hummus_renderer_rep::apply_shadow (SI x1, SI y1, SI x2, SI y2) {
  // debug_convert << "apply_shadow\n";
  (void) x1;
  (void) y1;
  (void) x2;
  (void) y2;
}

renderer
pdf_hummus_renderer_rep::shadow (picture& pic, SI x1, SI y1, SI x2, SI y2) {
  double old_zoomf= this->zoomf;
  set_zoom_factor (5.0 * PICTURE_ZOOM);
  renderer ren= renderer_rep::shadow (pic, x1, y1, x2, y2);
  set_zoom_factor (old_zoomf);
  return ren;
}

/******************************************************************************
 * user interface
 ******************************************************************************/

renderer
pdf_hummus_renderer (url pdf_file_name, int dpi, int nr_pages, string page_type,
                     bool landscape, double paper_w, double paper_h) {
  // cout << "Hummus print to " << pdf_file_name << " at " << dpi << " dpi\n";
  page_type= as_string (call ("standard-paper-size", object (page_type)));
  return tm_new<pdf_hummus_renderer_rep> (
      pdf_file_name, dpi, nr_pages, page_type, landscape, paper_w, paper_h);
}
